#!/bin/bash
# 缓存淘汰策略失效（缓存雪崩）自动处理脚本
# 适用于Redis等Key-Value缓存系统
FIX_CODE_ID="avalanche" 
# 支持Redis、Memcached等多种缓存系统，增强了环境适应性

# 配置参数 - 根据实际环境修改
CACHE_TYPE="redis"                   # 缓存类型 (redis/memcached)
CACHE_HOST="localhost"               # 缓存服务地址
CACHE_PORT=6379                      # 缓存服务端口
CACHE_PASSWORD=""                    # 缓存服务密码（无密码则留空）
CHECK_KEY_PREFIX="business:"         # 需要监控的业务缓存键前缀
HIT_RATE_THRESHOLD=0.7               # 缓存命中率阈值（低于此值触发处理）
DB_CONN_THRESHOLD=200                # 数据库连接数阈值（超过此值辅助判断）
MIN_EXPIRE=300                       # 最小过期时间（秒）
MAX_EXPIRE=1800                      # 最大过期时间（秒）
HOT_KEY_FILE="/etc/cache/hot_keys.txt" # 热点缓存键列表文件
LOG_FILE="/var/log/cache_management.log"
CACHE_SERVICE="redis"                # 缓存服务名称

# 日志函数
log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}

# 检查命令是否存在
command_exists() {
    command -v "$1" >/dev/null 2>&1
}

# 检查并安装必要工具
check_dependencies() {
    local missing=0
    local required_tools=("bc" "curl")
    
    # 根据缓存类型添加必要工具
    if [ "$CACHE_TYPE" = "redis" ]; then
        required_tools+=("redis-cli")
    elif [ "$CACHE_TYPE" = "memcached" ]; then
        required_tools+=("memcached-tool")
    fi
    
    # 检查必要工具
    for tool in "${required_tools[@]}"; do
        if ! command_exists "$tool"; then
            log "缺少必要工具: $tool"
            missing=1
        fi
    done
    
    if [ $missing -eq 1 ]; then
        log "尝试安装缺少的工具..."
        if command_exists "apt-get"; then
            apt-get update >/dev/null 2>&1
            apt-get install -y "${required_tools[@]}" >/dev/null 2>&1
        elif command_exists "yum"; then
            yum install -y "${required_tools[@]}" >/dev/null 2>&1
        else
            log "无法自动安装工具，不支持的包管理器"
            return 1
        fi
        
        # 再次检查
        for tool in "${required_tools[@]}"; do
            if ! command_exists "$tool"; then
                log "工具安装失败: $tool"
                return 1
            fi
        done
    fi
    
    return 0
}

# 执行Redis命令的封装函数
redis_cmd() {
    local cmd=$1
    if [ -z "$CACHE_PASSWORD" ]; then
        redis-cli -h "$CACHE_HOST" -p "$CACHE_PORT" $cmd
    else
        redis-cli -h "$CACHE_HOST" -p "$CACHE_PORT" -a "$CACHE_PASSWORD" $cmd
    fi
}

# 执行Memcached命令的封装函数
memcached_cmd() {
    local cmd=$1
    memcached-tool "$CACHE_HOST:$CACHE_PORT" $cmd
}

# 检查缓存服务是否正常运行
check_cache_alive() {
    log "检查$CACHE_TYPE服务是否运行"
    
    # 先检查进程
    if ! pgrep -f "$CACHE_TYPE" >/dev/null; then
        log "$CACHE_TYPE进程未运行"
        return 1
    fi
    
    # 再检查连接
    if [ "$CACHE_TYPE" = "redis" ]; then
        if redis_cmd "ping" | grep -q "PONG"; then
            return 0
        fi
    elif [ "$CACHE_TYPE" = "memcached" ]; then
        if echo "stats" | nc "$CACHE_HOST" "$CACHE_PORT" >/dev/null 2>&1; then
            return 0
        fi
    fi
    
    log "$CACHE_TYPE服务连接失败"
    return 1
}

# 获取缓存命中率
get_cache_hit_rate() {
    if [ "$CACHE_TYPE" = "redis" ]; then
        local info=$(redis_cmd "info stats")
        local keyspace_hits=$(echo "$info" | grep "keyspace_hits" | awk -F: '{print $2}')
        local keyspace_misses=$(echo "$info" | grep "keyspace_misses" | awk -F: '{print $2}')
        
        if [ -z "$keyspace_hits" ] || [ -z "$keyspace_misses" ]; then
            log "无法获取Redis缓存命中统计"
            return 1
        fi
        
        local total=$((keyspace_hits + keyspace_misses))
        if [ $total -eq 0 ]; then
            echo 1.0  # 无请求时默认命中率100%
            return 0
        fi
        
        echo "scale=4; $keyspace_hits / $total" | bc
    elif [ "$CACHE_TYPE" = "memcached" ]; then
        local stats=$(echo "stats" | nc "$CACHE_HOST" "$CACHE_PORT" 2>/dev/null)
        local get_hits=$(echo "$stats" | grep "get_hits" | awk '{print $2}')
        local get_misses=$(echo "$stats" | grep "get_misses" | awk '{print $2}')
        
        if [ -z "$get_hits" ] || [ -z "$get_misses" ]; then
            log "无法获取Memcached缓存命中统计"
            return 1
        fi
        
        local total=$((get_hits + get_misses))
        if [ $total -eq 0 ]; then
            echo 1.0
            return 0
        fi
        
        echo "scale=4; $get_hits / $total" | bc
    fi
}

# 检测是否发生缓存雪崩
detect_avalanche() {
    local hit_rate=$(get_cache_hit_rate)
    local db_conn=$(check_db_connections)
    
    log "当前缓存命中率: $hit_rate，数据库连接数: $db_conn"
    
    # 转换为数值比较（命中率低于阈值）
    if (( $(echo "$hit_rate < $HIT_RATE_THRESHOLD" | bc -l) )); then
        # 结合数据库连接数判断（可选）
        if [ -n "$db_conn" ] && [ $db_conn -gt $DB_CONN_THRESHOLD ]; then
            log "检测到缓存雪崩迹象（命中率低且数据库连接数高）"
            return 0
        elif [ -z "$db_conn" ]; then
            log "检测到缓存雪崩迹象（命中率低）"
            return 0
        fi
    fi
    
    return 1
}

# 调整缓存过期时间（增加随机值避免同时过期）
adjust_expire_time() {
    log "开始调整缓存过期时间"
    
    if [ "$CACHE_TYPE" = "redis" ]; then
        # 获取所有需要处理的缓存键
        local keys=$(redis_cmd "keys ${CHECK_KEY_PREFIX}*")
        
        if [ -z "$keys" ]; then
            log "未找到需要处理的Redis缓存键"
            return 1
        fi
        
        echo "$keys" | while read -r key; do
            # 生成随机过期时间（在MIN_EXPIRE到MAX_EXPIRE之间）
            local random_expire=$((MIN_EXPIRE + RANDOM % (MAX_EXPIRE - MIN_EXPIRE + 1)))
            redis_cmd "EXPIRE $key $random_expire"
            # 每处理100个键休息一下，避免缓存服务压力过大
            if (( RANDOM % 100 == 0 )); then
                sleep 1
            fi
        done
    elif [ "$CACHE_TYPE" = "memcached" ]; then
        log "Memcached不支持批量调整过期时间，建议在应用层实现随机过期策略"
        return 0
    fi
    
    log "缓存过期时间调整完成"
    return 0
}

# 预热热点缓存
preload_hot_cache() {
    log "开始预热热点缓存"
    
    if [ ! -f "$HOT_KEY_FILE" ]; then
        log "热点缓存键列表文件不存在: $HOT_KEY_FILE"
        return 1
    fi
    
    # 读取热点键列表并重新加载缓存
    while IFS= read -r key; do
        if [ -n "$key" ] && [[ "$key" != \#* ]]; then
            log "预热缓存键: $key"
            # 这里需要替换为实际的缓存加载命令
            # 示例: /usr/local/bin/load_cache.sh "$key"
            # 对于Redis，设置随机过期时间
            if [ "$CACHE_TYPE" = "redis" ]; then
                local random_expire=$((MIN_EXPIRE + RANDOM % (MAX_EXPIRE - MIN_EXPIRE + 1)))
                redis_cmd "EXPIRE $key $random_expire"
            fi
            sleep 0.1  # 控制预热速度
        fi
    done < "$HOT_KEY_FILE"
    
    log "热点缓存预热完成"
    return 0
}

# 临时调整缓存淘汰策略
adjust_eviction_policy() {
    log "调整$CACHE_TYPE缓存淘汰策略"
    
    if [ "$CACHE_TYPE" = "redis" ]; then
        # 获取当前淘汰策略
        local current_policy=$(redis_cmd "config get maxmemory-policy" | grep -A1 maxmemory-policy | tail -1)
        
        # 如果当前不是allkeys-lru或volatile-lru，进行调整
        if [ "$current_policy" != "allkeys-lru" ] && [ "$current_policy" != "volatile-lru" ]; then
            redis_cmd "config set maxmemory-policy allkeys-lru"
            log "已将Redis缓存淘汰策略从$current_policy调整为allkeys-lru"
            # 保存配置（持久化）
            redis_cmd "config rewrite"
        else
            log "Redis缓存淘汰策略已是最优: $current_policy"
        fi
    elif [ "$CACHE_TYPE" = "memcached" ]; then
        log "Memcached建议调整max_memory和evictions策略，需要通过配置文件修改"
        # 可以在这里添加修改memcached配置文件的逻辑
    fi
    
    return 0
}

# 检查数据库连接数（以MySQL为例）
check_db_connections() {
    # 实际使用时需替换为适合自己数据库的查询命令
    if command_exists "mysql"; then
        local conn_count=$(mysql -u root -p'your_db_password' -e "show status like 'Threads_connected';" 2>/dev/null | grep Threads_connected | awk '{print $2}')
        echo "$conn_count"
    else
        log "未安装mysql客户端，无法获取数据库连接数"
        echo ""
    fi
}

# 重启缓存服务
restart_cache_service() {
    log "尝试重启$CACHE_TYPE服务"
    
    # 尝试多种服务管理方式
    if systemctl is-active --quiet "$CACHE_SERVICE"; then
        systemctl restart "$CACHE_SERVICE"
    elif command_exists "service"; then
        service "$CACHE_SERVICE" restart
    else
        # 直接重启进程
        log "使用服务管理命令失败，尝试直接重启进程"
        pkill -f "$CACHE_TYPE"
        sleep 2
        # 尝试默认启动命令
        if [ "$CACHE_TYPE" = "redis" ]; then
            redis-server --daemonize yes
        elif [ "$CACHE_TYPE" = "memcached" ]; then
            memcached -d -p "$CACHE_PORT" -l "$CACHE_HOST"
        fi
    fi
    
    sleep 5
    if check_cache_alive; then
        log "$CACHE_TYPE服务重启成功"
        return 0
    else
        log "$CACHE_TYPE服务重启失败"
        return 1
    fi
}

# 主逻辑
main() {
    log "开始缓存雪崩检测与处理"
    
    # 检查依赖工具
    if ! check_dependencies; then
        log "缺少必要工具，无法继续处理"
        exit 1
    fi
    
    # 检查缓存服务状态
    if ! check_cache_alive; then
        log "$CACHE_TYPE服务不可用，尝试重启"
        if ! restart_cache_service; then
            log "$CACHE_TYPE服务重启失败，需要人工干预"
            exit 1
        fi
    fi
    
    # 检测是否发生缓存雪崩
    if ! detect_avalanche; then
        log "未检测到缓存雪崩，缓存状态正常"
        exit 0
    fi
    
    # 执行修复流程
    log "启动缓存雪崩修复流程"
    
    # 步骤1: 调整缓存淘汰策略
    adjust_eviction_policy
    
    # 步骤2: 调整现有缓存过期时间（增加随机性）
    adjust_expire_time
    
    # 步骤3: 预热热点缓存
    preload_hot_cache
    
    # 检查修复效果
    sleep 30  # 等待一段时间让修复生效
    local new_hit_rate=$(get_cache_hit_rate)
    log "修复后缓存命中率: $new_hit_rate"
    
    if (( $(echo "$new_hit_rate >= $HIT_RATE_THRESHOLD" | bc -l) )); then
        log "缓存雪崩修复成功"
        exit 0
    else
        log "缓存雪崩修复效果不明显，建议人工干预"
        exit 1
    fi
}

# 执行主逻辑
main
    